/* utils */
import { isArray, isFunction, isObject, isEmpty } from '@src/util/type'
import { camelize } from '@src/util/format/string'
/* mixins */
import { SlotsMixin } from '@src/mixins/slotsMixin'
import MethodsMixin from '@src/mixins/methodsMixin'
/* vue */
import Vue, { VNode, VueConstructor, ComponentOptions, RenderContext } from 'vue'
import { DefaultProps, FunctionComponent } from '@src/util/types'

const defaultProps = ['value']

export type Props = Array<string> | Record<string, any> | undefined

export interface PublinkComponentOptions extends ComponentOptions<Vue> {
  functional?: boolean;
  install?: (Vue: VueConstructor) => void;
}

// tsx基础属性
export type TsxBaseProps<Slots> = {
  key: string | number;
  props: any;
  class: any;
  style: string | object[] | object;
  scopedSlots: Slots;
};

// tsx 组件
export type TsxComponent<Props, Events, Slots> = (props: Partial<Props & Events & TsxBaseProps<Slots>>) => VNode;

/**
 * @description: vue注册方法
 * @param {ComponentOptions} this
 * @param {VueConstructor} Vue
 * @return {void}
 */
function install (this: ComponentOptions<Vue>, Vue: VueConstructor): void {
  const { name } = this
  Vue.component(name as string, this)
  Vue.component(camelize(`-${name}`), this)
}

function getProps (originProps: Props): Props {
  // @ts-ignore
  let value: Record<string, any> | string | undefined = originProps?.value
  
  // 处理数组props
  if (isArray(originProps)) {
    return originProps?.concat(defaultProps)
  }
  // 处理对象props且value属性为空
  else if (isObject(originProps) && isEmpty(value)) {
    return {
      ...originProps,
      value: {}
    }
  }
  // 处理空props
  else if (isEmpty(originProps)) {
    return defaultProps
  }
  
  return originProps
}

// 统一 slots & scopedSlots
export function unifySlots (context: RenderContext) {
  // 低版本 vue 使用 data.scopedSlots
  // @ts-ignore
  const scopedSlots = context.scopedSlots || context.data.scopedSlots || {}
  const slots = context.slots()
  
  Object.keys(slots).forEach((key) => {
    if (!scopedSlots[key]) {
      scopedSlots[key] = () => slots[key]
    }
  })
  
  return scopedSlots
}

/**
 * @description: 转换成函数式组件
 * !!! 将在vue3移除
 * @param {FunctionComponent} 函数式组件
 * @return {PublinkComponentOptions} vue options 配置对象
*/
function transformFunctionComponent (
  pure: FunctionComponent
): PublinkComponentOptions {
  return {
    functional: true,
    props: pure.props,
    model: pure.model,
    render: (h, context): any => pure(h, context.props, unifySlots(context), context)
  }
}

/**
 * @description: 创建组件
 * sfc === Single File Component 单文件组件
 * @param {string} name 组件名称
 * @return {Function} 创建组件高阶函数
*/
export function createComponent (name: string) {
  return (
    function <Props = DefaultProps, Events = {}, Slots = {}> (
      sfc: PublinkComponentOptions | FunctionComponent | any
    ): TsxComponent<Props, Events, Slots> {
      
      // 如果是函数，则转换为函数式组件
      if (isFunction(sfc)) {
        sfc = transformFunctionComponent(sfc)
      }
      
      // options配置项式组件 添加 mixins 混入
      if (!sfc.functional) {
        sfc.mixins = sfc.mixins || []
        sfc.mixins.push(SlotsMixin)
        sfc.mixins.push(MethodsMixin)
      }
      // 组件名称
      sfc.name = name
      // 组件初始化安装方法
      sfc.install = install
      // 暂时写死的，因为element没有额外的自定义
      sfc.model = {
        prop: 'value',
        event: 'input'
      }
      sfc.props = getProps(sfc.props)
      
      return sfc as TsxComponent<Props, Events, Slots>
    }
  )
}
